package picounit.features.classloader.mutator;

import moved.org.objectweb.asm.AnnotationVisitor;
import moved.org.objectweb.asm.Attribute;
import moved.org.objectweb.asm.ClassReader;
import moved.org.objectweb.asm.ClassVisitor;
import moved.org.objectweb.asm.FieldVisitor;
import moved.org.objectweb.asm.Label;
import moved.org.objectweb.asm.MethodVisitor;
import moved.org.objectweb.asm.Opcodes;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;

public class TraceClassVisitor implements ClassVisitor, Opcodes {
	private final PrintStream printStream;

	public static void main(String[] args) throws Exception {
		new TraceClassVisitor().trace(new FileInputStream(args[0]));
	}

	public TraceClassVisitor() {
		this(System.out);
	}

	public TraceClassVisitor(PrintStream printStream) {
		this.printStream = printStream;
	}

	public void trace(byte[] byteCode) {
		try {
			trace(new ByteArrayInputStream(byteCode));
		}
		catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void trace(InputStream inputStream) throws IOException {
		new ClassReader(inputStream).accept(this, false);
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// ClassVisitor
	///////////////////////////////////////////////////////////////////////////////////////////////

	public void visitSource(String source, String debug) {
		print(source, debug);
	}

	public void visit(int version, int access, String name, String signature, String superName,
		String[] interfaces) {
		
		print(version, access, name, signature, superName);
	}

	public void visitAttribute(Attribute attr) {
		print(attr);
	}

	public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
		print(desc, visible);
		
		return new TraceAnnotationVisitor();
	}

	public FieldVisitor visitField(int access, String name, String desc, String signature,
		Object value) {
		
		print(access, name, desc, signature);

		return new TraceFieldVisitor();
	}

	public void visitInnerClass(String name, String outerName, String innerName, int access) {
		print(name, outerName, innerName, access);
	}
	
	public MethodVisitor visitMethod(int access, String name, String desc, String signature,
		String[] exceptions) {
		
		print(access, name, desc, signature, exceptions);

		return new TraceMethodVisitor();
	}

	public void visitOuterClass(String owner, String name, String desc) {
		print(owner, name, desc);
	}

	public void visitEnd() {
		print();
	}

	public class TraceAnnotationVisitor implements AnnotationVisitor {
		public AnnotationVisitor visitAnnotation(String name, String desc) {
			print(name, desc);

			return new TraceAnnotationVisitor();
		}

		public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
			print(desc, visible);

			return this;
		}

		public void visit(String name, Object value) {
			print(name, value);
		}

		public void visitEnum(String name, String desc, String value) {
			print(name, desc, value);
		}

		public AnnotationVisitor visitArray(String name) {
			print(name);

			return this;
		}

		public void visitEnd() {
			print();
		}
		
		private void print(Object ... args) {
			printMethod(new Throwable().getStackTrace()[1].getMethodName(), args);
		}
	}
	
	public class TraceFieldVisitor implements FieldVisitor {
		public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
			print(desc, visible);
			
			return new TraceAnnotationVisitor();
		}

		public void visitAttribute(Attribute attr) {
			print(attr);
		}

		public void visitEnd() {
		}
		
		private void print(Object ... args) {
			printMethod(new Throwable().getStackTrace()[1].getMethodName(), args);
		}
	}
	
	public class TraceMethodVisitor implements MethodVisitor {
		public void visitCode() {
			print();
		}

		public void visitAttribute(Attribute attr) {
			print(attr);
		}
		
		public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
			print(desc, visible);
			
			return new TraceAnnotationVisitor();
		}

		public AnnotationVisitor visitAnnotationDefault() {
			print();
			
			return new TraceAnnotationVisitor();
		}

		public void visitFieldInsn(int opcode, String owner, String name, String desc) {
			print(lookup(opcode), owner, name, desc);
		}

		public void visitIincInsn(int var, int increment) {
			print(var, increment);
		}

		public void visitInsn(int opcode) {
			print(lookup(opcode));
		}

		public void visitIntInsn(int opcode, int operand) {
			print(lookup(opcode), operand);
		}

		public void visitJumpInsn(int opcode, Label label) {
			print(lookup(opcode), label);
		}

		public void visitLabel(Label label) {
//			print(label);
		}

		public void visitLdcInsn(Object cst) {
			print(cst);
		}

		public void visitLineNumber(int line, Label start) {
//			print(line, start);
		}

		public void visitLocalVariable(String name, String desc, String signature, Label start, Label end,
			int index) {

			print(name, desc, signature, start, end, index);
		}

		public void visitLookupSwitchInsn(Label dflt, int keys[], Label labels[]) {
			print(dflt, keys, labels);
		}

		public void visitMaxs(int maxStack, int maxLocals) {
			print(maxStack, maxLocals);
		}

		public void visitMethodInsn(int opcode, String owner, String name, String desc) {
			print(lookup(opcode), owner, name, desc);
		}

		private String lookup(int opcode) {
			return opcodeMap.get(opcode);
		}

		public void visitMultiANewArrayInsn(String desc, int dims) {
			print(desc, dims);
		}

		public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
			print(parameter, desc, visible);

			return new TraceAnnotationVisitor();
		}

		public void visitTableSwitchInsn(int min, int max, Label dflt, Label labels[]) {
			print(min, max, dflt, labels);
		}

		public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
			print(start, end, handler, type);
		}

		public void visitTypeInsn(int opcode, String desc) {
			print(lookup(opcode), desc);
		}

		public void visitVarInsn(int opcode, int var) {
			print(lookup(opcode), var);
		}
		
		public void visitEnd() {
			print();
		}
		
		private void print(Object ... args) {
			printMethod(new Throwable().getStackTrace()[1].getMethodName(), args);
		}
	}

	private void print(Object ... args) {
		printMethod(new Throwable().getStackTrace()[1].getMethodName(), args);
	}

	private void printMethod(String methodName, Object... args) {
		printStream.print(methodName + '(');
		
		if (args != null) {
			for (int index = 0; index < args.length; ++index) {
				if (index != 0) {
					printStream.print(", ");
				}
				printStream.print(args[index]);
			}
		}
		
		printStream.println(')');
	}

	private final Map<Integer, String> opcodeMap = getOpcodeMap();

	private Map<Integer, String> getOpcodeMap() {
		HashMap<Integer, String> opcodes = new HashMap<Integer, String>();

		opcodes.put(AALOAD, "AALOAD");
		opcodes.put(AASTORE, "AASTORE");
		opcodes.put(ACC_ABSTRACT, "ACC_ABSTRACT");
		opcodes.put(ACC_ANNOTATION, "ACC_ANNOTATION");
		opcodes.put(ACC_BRIDGE, "ACC_BRIDGE");
		opcodes.put(ACC_DEPRECATED, "ACC_DEPRECATED");
		opcodes.put(ACC_ENUM, "ACC_ENUM");
		opcodes.put(ACC_FINAL, "ACC_FINAL");
		opcodes.put(ACC_INTERFACE, "ACC_INTERFACE");
		opcodes.put(ACC_NATIVE, "ACC_NATIVE");
		opcodes.put(ACC_PRIVATE, "ACC_PRIVATE");
		opcodes.put(ACC_PROTECTED, "ACC_PROTECTED");
		opcodes.put(ACC_PUBLIC, "ACC_PUBLIC");
		opcodes.put(ACC_STATIC, "ACC_STATIC");
		opcodes.put(ACC_STRICT, "ACC_STRICT");
		opcodes.put(ACC_SUPER, "ACC_SUPER");
		opcodes.put(ACC_SYNCHRONIZED, "ACC_SYNCHRONIZED");
		opcodes.put(ACC_SYNTHETIC, "ACC_SYNTHETIC");
		opcodes.put(ACC_TRANSIENT, "ACC_TRANSIENT");
		opcodes.put(ACC_VARARGS, "ACC_VARARGS");
		opcodes.put(ACC_VOLATILE, "ACC_VOLATILE");
		opcodes.put(ACONST_NULL, "ACONST_NULL");
		opcodes.put(ALOAD, "ALOAD");
		opcodes.put(ANEWARRAY, "ANEWARRAY");
		opcodes.put(ARETURN, "ARETURN");
		opcodes.put(ARRAYLENGTH, "ARRAYLENGTH");
		opcodes.put(ASTORE, "ASTORE");
		opcodes.put(ATHROW, "ATHROW");
		opcodes.put(BALOAD, "BALOAD");
		opcodes.put(BASTORE, "BASTORE");
		opcodes.put(BIPUSH, "BIPUSH");
		opcodes.put(CALOAD, "CALOAD");
		opcodes.put(CASTORE, "CASTORE");
		opcodes.put(CHECKCAST, "CHECKCAST");
		opcodes.put(D2F, "D2F");
		opcodes.put(D2I, "D2I");
		opcodes.put(D2L, "D2L");
		opcodes.put(DADD, "DADD");
		opcodes.put(DALOAD, "DALOAD");
		opcodes.put(DASTORE, "DASTORE");
		opcodes.put(DCMPG, "DCMPG");
		opcodes.put(DCMPL, "DCMPL");
		opcodes.put(DCONST_0, "DCONST_0");
		opcodes.put(DCONST_1, "DCONST_1");
		opcodes.put(DDIV, "DDIV");
		opcodes.put(DLOAD, "DLOAD");
		opcodes.put(DMUL, "DMUL");
		opcodes.put(DNEG, "DNEG");
		opcodes.put(DREM, "DREM");
		opcodes.put(DRETURN, "DRETURN");
		opcodes.put(DSTORE, "DSTORE");
		opcodes.put(DSUB, "DSUB");
		opcodes.put(DUP, "DUP");
		opcodes.put(DUP_X1, "DUP_X1");
		opcodes.put(DUP_X2, "DUP_X2");
		opcodes.put(DUP2, "DUP2");
		opcodes.put(DUP2_X1, "DUP2_X1");
		opcodes.put(DUP2_X2, "DUP2_X2");
		opcodes.put(F2D, "F2D");
		opcodes.put(F2I, "F2I");
		opcodes.put(F2L, "F2L");
		opcodes.put(FADD, "FADD");
		opcodes.put(FALOAD, "FALOAD");
		opcodes.put(FASTORE, "FASTORE");
		opcodes.put(FCMPG, "FCMPG");
		opcodes.put(FCMPL, "FCMPL");
		opcodes.put(FCONST_0, "FCONST_0");
		opcodes.put(FCONST_1, "FCONST_1");
		opcodes.put(FCONST_2, "FCONST_2");
		opcodes.put(FDIV, "FDIV");
		opcodes.put(FLOAD, "FLOAD");
		opcodes.put(FMUL, "FMUL");
		opcodes.put(FNEG, "FNEG");
		opcodes.put(FREM, "FREM");
		opcodes.put(FRETURN, "FRETURN");
		opcodes.put(FSTORE, "FSTORE");
		opcodes.put(FSUB, "FSUB");
		opcodes.put(GETFIELD, "GETFIELD");
		opcodes.put(GETSTATIC, "GETSTATIC");
		opcodes.put(GOTO, "GOTO");
		opcodes.put(I2B, "I2B");
		opcodes.put(I2C, "I2C");
		opcodes.put(I2D, "I2D");
		opcodes.put(I2F, "I2F");
		opcodes.put(I2L, "I2L");
		opcodes.put(I2S, "I2S");
		opcodes.put(IADD, "IADD");
		opcodes.put(IALOAD, "IALOAD");
		opcodes.put(IAND, "IAND");
		opcodes.put(IASTORE, "IASTORE");
		opcodes.put(ICONST_0, "ICONST_0");
		opcodes.put(ICONST_1, "ICONST_1");
		opcodes.put(ICONST_2, "ICONST_2");
		opcodes.put(ICONST_3, "ICONST_3");
		opcodes.put(ICONST_4, "ICONST_4");
		opcodes.put(ICONST_5, "ICONST_5");
		opcodes.put(ICONST_M1, "ICONST_M1");
		opcodes.put(IDIV, "IDIV");
		opcodes.put(IF_ACMPEQ, "IF_ACMPEQ");
		opcodes.put(IF_ACMPNE, "IF_ACMPNE");
		opcodes.put(IF_ICMPEQ, "IF_ICMPEQ");
		opcodes.put(IF_ICMPGE, "IF_ICMPGE");
		opcodes.put(IF_ICMPGT, "IF_ICMPGT");
		opcodes.put(IF_ICMPLE, "IF_ICMPLE");
		opcodes.put(IF_ICMPLT, "IF_ICMPLT");
		opcodes.put(IF_ICMPNE, "IF_ICMPNE");
		opcodes.put(IFEQ, "IFEQ");
		opcodes.put(IFGE, "IFGE");
		opcodes.put(IFGT, "IFGT");
		opcodes.put(IFLE, "IFLE");
		opcodes.put(IFLT, "IFLT");
		opcodes.put(IFNE, "IFNE");
		opcodes.put(IFNONNULL, "IFNONNULL");
		opcodes.put(IFNULL, "IFNULL");
		opcodes.put(IINC, "IINC");
		opcodes.put(ILOAD, "ILOAD");
		opcodes.put(IMUL, "IMUL");
		opcodes.put(INEG, "INEG");
		opcodes.put(INSTANCEOF, "INSTANCEOF");
		opcodes.put(INVOKEINTERFACE, "INVOKEINTERFACE");
		opcodes.put(INVOKESPECIAL, "INVOKESPECIAL");
		opcodes.put(INVOKESTATIC, "INVOKESTATIC");
		opcodes.put(INVOKEVIRTUAL, "INVOKEVIRTUAL");
		opcodes.put(IOR, "IOR");
		opcodes.put(IREM, "IREM");
		opcodes.put(IRETURN, "IRETURN");
		opcodes.put(ISHL, "ISHL");
		opcodes.put(ISHR, "ISHR");
		opcodes.put(ISTORE, "ISTORE");
		opcodes.put(ISUB, "ISUB");
		opcodes.put(IUSHR, "IUSHR");
		opcodes.put(IXOR, "IXOR");
		opcodes.put(JSR, "JSR");
		opcodes.put(L2D, "L2D");
		opcodes.put(L2F, "L2F");
		opcodes.put(L2I, "L2I");
		opcodes.put(LADD, "LADD");
		opcodes.put(LALOAD, "LALOAD");
		opcodes.put(LAND, "LAND");
		opcodes.put(LASTORE, "LASTORE");
		opcodes.put(LCMP, "LCMP");
		opcodes.put(LCONST_0, "LCONST_0");
		opcodes.put(LCONST_1, "LCONST_1");
		opcodes.put(LDC, "LDC");
		opcodes.put(LDIV, "LDIV");
		opcodes.put(LLOAD, "LLOAD");
		opcodes.put(LMUL, "LMUL");
		opcodes.put(LNEG, "LNEG");
		opcodes.put(LOOKUPSWITCH, "LOOKUPSWITCH");
		opcodes.put(LOR, "LOR");
		opcodes.put(LREM, "LREM");
		opcodes.put(LRETURN, "LRETURN");
		opcodes.put(LSHL, "LSHL");
		opcodes.put(LSHR, "LSHR");
		opcodes.put(LSTORE, "LSTORE");
		opcodes.put(LSUB, "LSUB");
		opcodes.put(LUSHR, "LUSHR");
		opcodes.put(LXOR, "LXOR");
		opcodes.put(MONITORENTER, "MONITORENTER");
		opcodes.put(MONITOREXIT, "MONITOREXIT");
		opcodes.put(MULTIANEWARRAY, "MULTIANEWARRAY");
		opcodes.put(NEW, "NEW");
		opcodes.put(NEWARRAY, "NEWARRAY");
		opcodes.put(NOP, "NOP");
		opcodes.put(POP, "POP");
		opcodes.put(POP2, "POP2");
		opcodes.put(PUTFIELD, "PUTFIELD");
		opcodes.put(PUTSTATIC, "PUTSTATIC");
		opcodes.put(RET, "RET");
		opcodes.put(RETURN, "RETURN");
		opcodes.put(SALOAD, "SALOAD");
		opcodes.put(SASTORE, "SASTORE");
		opcodes.put(SIPUSH, "SIPUSH");
		opcodes.put(SWAP, "SWAP");
		opcodes.put(T_BOOLEAN, "T_BOOLEAN");
		opcodes.put(T_BYTE, "T_BYTE");
		opcodes.put(T_CHAR, "T_CHAR");
		opcodes.put(T_DOUBLE, "T_DOUBLE");
		opcodes.put(T_FLOAT, "T_FLOAT");
		opcodes.put(T_INT, "T_INT");
		opcodes.put(T_LONG, "T_LONG");
		opcodes.put(T_SHORT, "T_SHORT");
		opcodes.put(TABLESWITCH, "TABLESWITCH");
		opcodes.put(V1_1, "V1_1");
		opcodes.put(V1_2, "V1_2");
		opcodes.put(V1_3, "V1_3");
		opcodes.put(V1_4, "V1_4");
		opcodes.put(V1_5, "V1_5");
		
		return opcodes;
	}
}
